<template><div><p>本文档最新版为 <a href="https://learnku.com/docs/laravel/10.x" target="_blank" rel="noopener noreferrer">10.x</a>，旧版本可能放弃维护，推荐阅读最新版！</p>
<h2 id="laravel-sanctum" tabindex="-1"><a class="header-anchor" href="#laravel-sanctum"><span>Laravel Sanctum</span></a></h2>
<ul>
<li><a href="#introduction">简介</a>
<ul>
<li><a href="#how-it-works">工作原理</a></li>
</ul>
</li>
<li><a href="#installation">安装</a></li>
<li><a href="#configuration">配置</a>
<ul>
<li><a href="#overriding-default-models">覆盖默认模型</a></li>
</ul>
</li>
<li><a href="#api-token-authentication">API 令牌认证</a>
<ul>
<li><a href="#issuing-api-tokens">发放 API 令牌</a></li>
<li><a href="#token-abilities">令牌权限</a></li>
<li><a href="#protecting-routes">保护路由</a></li>
<li><a href="#revoking-tokens">撤销令牌</a></li>
<li><a href="#token-expiration">令牌过期</a></li>
</ul>
</li>
<li><a href="#spa-authentication">单页面应用认证</a>
<ul>
<li><a href="#spa-configuration">配置</a></li>
<li><a href="#spa-authenticating">认证</a></li>
<li><a href="#protecting-spa-routes">保护路由</a></li>
<li><a href="#authorizing-private-broadcast-channels">授权私有广播频道</a></li>
</ul>
</li>
<li><a href="#mobile-application-authentication">移动应用认证</a>
<ul>
<li><a href="#issuing-mobile-api-tokens">发放 API 令牌</a></li>
<li><a href="#protecting-mobile-api-routes">保护路由</a></li>
<li><a href="#revoking-mobile-api-tokens">撤销令牌</a></li>
</ul>
</li>
<li><a href="#testing">测试</a></li>
</ul>
<h2 id="简介" tabindex="-1"><a class="header-anchor" href="#简介"><span>简介</span></a></h2>
<p><a href="https://github.com/laravel/sanctum" target="_blank" rel="noopener noreferrer">Laravel Sanctum</a> 为单页应用（SPA）、移动应用以及基于令牌的简易 API 提供了一个轻量级的认证系统。Sanctum 允许应用的每个用户为他们的账户生成多个 API 令牌。这些令牌可以被授予能力/范围，指定令牌允许执行的操作。</p>
<h3 id="工作原理" tabindex="-1"><a class="header-anchor" href="#工作原理"><span>工作原理</span></a></h3>
<p>Laravel Sanctum 旨在解决两个独立的问题。在深入探讨该库之前，让我们先讨论每个问题。</p>
<h4 id="api-令牌" tabindex="-1"><a class="header-anchor" href="#api-令牌"><span>API 令牌</span></a></h4>
<p>首先，Sanctum 是一个简单的包，您可以使用它为用户发放 API 令牌，无需复杂的 OAuth。这个功能的灵感来自 GitHub 和其他发放“个人访问令牌”的应用。例如，想象一下您的应用中的“账户设置”页面，用户可以在此页面为他们的账户生成一个 API 令牌。您可以使用 Sanctum 来生成和管理这些令牌。这些令牌通常具有非常长的过期时间（数年），但用户可以随时手动撤销。</p>
<h2 id="laravel-sanctum-1" tabindex="-1"><a class="header-anchor" href="#laravel-sanctum-1"><span>Laravel Sanctum</span></a></h2>
<p>Laravel Sanctum 通过在单一数据库表中存储用户 API 令牌，并通过应包含有效 API 令牌的 <code v-pre>Authorization</code> 头部来认证传入的 HTTP 请求，提供了这一功能。</p>
<h4 id="单页面应用认证" tabindex="-1"><a class="header-anchor" href="#单页面应用认证"><span>单页面应用认证</span></a></h4>
<p>其次，Sanctum 提供了一种简单的方法来认证需要与 Laravel 驱动的 API 通信的单页应用程序（SPA）。这些 SPA 可能与你的 Laravel 应用存在于同一个仓库中，或者可能是一个完全独立的仓库，例如使用 Vue CLI 或 Next.js 应用程序创建的 SPA。</p>
<p>对于这一功能，Sanctum 不使用任何类型的令牌。相反，Sanctum 使用 Laravel 内置的基于 cookie 的会话认证服务。通常，Sanctum 利用 Laravel 的 <code v-pre>web</code> 认证守卫来实现这一点。这提供了 CSRF 保护、会话认证的好处，同时还防止了通过 XSS 泄露认证凭据。</p>
<p>当传入请求来自你自己的 SPA 前端时，Sanctum 仅尝试使用 cookie 进行认证。当 Sanctum 检查一个传入的 HTTP 请求时，它首先检查认证 cookie，如果不存在，Sanctum 然后会检查 <code v-pre>Authorization</code> 头部以寻找有效的 API 令牌。</p>
<blockquote>
<p>[!NOTE]<br>
仅使用 Sanctum 进行 API 令牌认证或仅用于 SPA 认证都是完全可以的。使用 Sanctum 并不意味着你必须使用它提供的两个功能。</p>
</blockquote>
<h2 id="安装" tabindex="-1"><a class="header-anchor" href="#安装"><span>安装</span></a></h2>
<p>你可以通过 <code v-pre>install:api</code> Artisan 命令安装 Laravel Sanctum：</p>
<div class="language-bash line-numbers-mode" data-highlighter="prismjs" data-ext="sh" data-title="sh"><pre v-pre class="language-bash"><code><span class="line">php artisan install:api</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>接下来，如果你计划使用 Sanctum 来认证一个 SPA，请参阅本文档的<a href="#spa-authentication">单页面应用认证</a>部分。</p>
<h2 id="配置" tabindex="-1"><a class="header-anchor" href="#配置"><span>配置</span></a></h2>
<h2 id="配置-1" tabindex="-1"><a class="header-anchor" href="#配置-1"><span>配置</span></a></h2>
<h3 id="重写默认模型" tabindex="-1"><a class="header-anchor" href="#重写默认模型"><span>重写默认模型</span></a></h3>
<p>虽然通常不需要，但你可以自由扩展 Sanctum 内部使用的 <code v-pre>PersonalAccessToken</code> 模型：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>PersonalAccessToken</span> <span class="token keyword">as</span> SanctumPersonalAccessToken<span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name-definition class-name">PersonalAccessToken</span> <span class="token keyword">extends</span> <span class="token class-name">SanctumPersonalAccessToken</span></span>
<span class="line"><span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// ...</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后，你可以通过 Sanctum 提供的 <code v-pre>usePersonalAccessTokenModel</code> 方法指定使用你的自定义模型。通常，你应该在应用程序的 <code v-pre>AppServiceProvider</code> 文件的 <code v-pre>boot</code> 方法中调用此方法：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Models<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>PersonalAccessToken</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>Sanctum</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token doc-comment comment">/**</span>
<span class="line"> * 启动任何应用服务。</span>
<span class="line"> */</span></span>
<span class="line"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">boot</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span></span>
<span class="line"><span class="token punctuation">{</span></span>
<span class="line">    <span class="token class-name static-context">Sanctum</span><span class="token operator">::</span><span class="token function">usePersonalAccessTokenModel</span><span class="token punctuation">(</span><span class="token class-name static-context">PersonalAccessToken</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="api-令牌认证" tabindex="-1"><a class="header-anchor" href="#api-令牌认证"><span>API 令牌认证</span></a></h2>
<blockquote>
<p>[!NOTE]<br>
你不应使用 API 令牌来认证你自己的第一方 SPA。相反，应使用 Sanctum 的内置<a href="#spa-authentication">单页面应用认证功能</a>。</p>
</blockquote>
<h3 id="发放-api-令牌" tabindex="-1"><a class="header-anchor" href="#发放-api-令牌"><span>发放 API 令牌</span></a></h3>
<p>Sanctum 允许你发放可用于认证应用程序 API 请求的 API 令牌/个人访问令牌。在使用 API 令牌发出请求时，令牌应包含在 <code v-pre>Authorization</code> 头部作为一个 <code v-pre>Bearer</code> 令牌。</p>
<p>要开始为用户发放令牌，你的用户模型应使用 <code v-pre>Laravel\Sanctum\HasApiTokens</code> 特性：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>HasApiTokens</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name-definition class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Authenticatable</span></span>
<span class="line"><span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">use</span> <span class="token package">HasApiTokens</span><span class="token punctuation">,</span> HasFactory<span class="token punctuation">,</span> Notifiable<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要发放一个令牌，你可以使用 <code v-pre>createToken</code> 方法。<code v-pre>createToken</code> 方法返回一个 <code v-pre>Laravel\Sanctum\NewAccessToken</code> 实例。API 令牌在存储到你的数据库之前会使用 SHA-256 哈希加密，但你可以使用 <code v-pre>NewAccessToken</code> 实例的 <code v-pre>plainTextToken</code> 属性访问令牌的明文值。令牌创建后，你应立即向用户显示此值：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/tokens/create'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$token</span> <span class="token operator">=</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">createToken</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token operator">-></span><span class="token property">token_name</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'token'</span> <span class="token operator">=></span> <span class="token variable">$token</span><span class="token operator">-></span><span class="token property">plainTextToken</span><span class="token punctuation">]</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>你可以通过 <code v-pre>HasApiTokens</code> 特性提供的 <code v-pre>tokens</code> Eloquent 关系访问用户的所有令牌：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$user</span><span class="token operator">-></span><span class="token property">tokens</span> <span class="token keyword">as</span> <span class="token variable">$token</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// ...</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="令牌权限" tabindex="-1"><a class="header-anchor" href="#令牌权限"><span>令牌权限</span></a></h3>
<p>Sanctum 允许你为令牌分配“权限”。权限的作用与 OAuth 的“范围”类似。你可以将字符串权限数组作为第二个参数传递给 <code v-pre>createToken</code> 方法：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token variable">$user</span><span class="token operator">-></span><span class="token function">createToken</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'token-name'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'server:update'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">plainTextToken</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>在处理由 Sanctum 认证的传入请求时，你可以使用 <code v-pre>tokenCan</code> 方法确定令牌是否具有给定的权限：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$user</span><span class="token operator">-></span><span class="token function">tokenCan</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'server:update'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// ...</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="令牌权限中间件" tabindex="-1"><a class="header-anchor" href="#令牌权限中间件"><span>令牌权限中间件</span></a></h4>
<p>Sanctum 还包括两个中间件，可用于验证传入请求是否使用了被授予特定权限的令牌进行认证。要开始，请在应用程序的 <code v-pre>bootstrap/app.php</code> 文件中定义以下中间件别名：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Middleware<span class="token punctuation">\</span>CheckAbilities</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Middleware<span class="token punctuation">\</span>CheckForAnyAbility</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token operator">-></span><span class="token function">withMiddleware</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Middleware</span> <span class="token variable">$middleware</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$middleware</span><span class="token operator">-></span><span class="token function">alias</span><span class="token punctuation">(</span><span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'abilities'</span> <span class="token operator">=></span> <span class="token class-name static-context">CheckAbilities</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'ability'</span> <span class="token operator">=></span> <span class="token class-name static-context">CheckForAnyAbility</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code v-pre>abilities</code> 中间件可以分配给一个路由，以验证传入请求的令牌是否具有所有列出的权限：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/orders'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// 令牌具有“检查状态”和“下订单”权限...</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">middleware</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'abilities:check-status,place-orders'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code v-pre>ability</code> 中间件可以分配给一个路由，以验证传入请求的令牌是否至少具有列出的某个权限：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/orders'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// 令牌具有“检查状态”或“下订单”权限...</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">middleware</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'ability:check-status,place-orders'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="首方-ui-发起的请求" tabindex="-1"><a class="header-anchor" href="#首方-ui-发起的请求"><span>首方 UI 发起的请求</span></a></h4>
<p>为了方便起见，如果传入的认证请求来自你的首方单页面应用（SPA），并且你使用的是 Sanctum 的内置<a href="#spa-authentication">单页面应用认证</a>，那么 <code v-pre>tokenCan</code> 方法将始终返回 <code v-pre>true</code>。</p>
<p>然而，这并不意味着你的应用必须允许用户执行该操作。通常，你的应用的<a href="https://learnku.com/docs/laravel/11.x/authorization#creating-policies" target="_blank" rel="noopener noreferrer">授权策略</a>将决定令牌是否被授予权限执行相关操作，并检查用户实例本身是否应被允许执行该操作。</p>
<p>例如，假设有一个管理服务器的应用，这可能意味着需要检查令牌是否有权更新服务器<strong>并且</strong>服务器属于该用户：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">id</span> <span class="token operator">===</span> <span class="token variable">$server</span><span class="token operator">-></span><span class="token property">user_id</span> <span class="token operator">&amp;&amp;</span></span>
<span class="line">       <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">tokenCan</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'server:update'</span><span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>起初，允许 <code v-pre>tokenCan</code> 方法被调用并始终对首方 UI 发起的请求返回 <code v-pre>true</code> 可能看起来很奇怪；然而，能够始终假设一个 API 令牌是可用的，并可以通过 <code v-pre>tokenCan</code> 方法检查，这是非常方便的。采取这种方法，你可以始终在应用的授权策略中调用 <code v-pre>tokenCan</code> 方法，而不用担心请求是由应用的 UI 触发的，还是由你的 API 的第三方消费者发起的。</p>
<h3 id="保护路由" tabindex="-1"><a class="header-anchor" href="#保护路由"><span>保护路由</span></a></h3>
<p>为了保护路由，确保所有传入请求都必须经过认证，你应该在你的 <code v-pre>routes/web.php</code> 和 <code v-pre>routes/api.php</code> 路由文件中，将 <code v-pre>sanctum</code> 认证守卫附加到你的受保护路由上。这个守卫将确保传入请求要么是有状态的、使用 Cookie 进行认证的请求，要么如果请求来自第三方，则包含有效的 API 令牌头。</p>
<p>你可能想知道为什么我们建议你在应用程序的 <code v-pre>routes/web.php</code> 文件中使用 <code v-pre>sanctum</code> 守卫来认证路由。请记住，Sanctum 首先会尝试使用 Laravel 的标准会话认证 Cookie 来认证传入的请求。如果没有该 Cookie，Sanctum 则会尝试使用请求的 <code v-pre>Authorization</code> 头部中的令牌来认证请求。此外，使用 Sanctum 认证所有请求确保我们始终可以在当前认证的用户实例上调用 <code v-pre>tokenCan</code> 方法：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/user'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">return</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">middleware</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="撤销令牌" tabindex="-1"><a class="header-anchor" href="#撤销令牌"><span>撤销令牌</span></a></h3>
<p>你可以通过使用 <code v-pre>Laravel\Sanctum\HasApiTokens</code> 特性提供的 <code v-pre>tokens</code> 关系从数据库中删除令牌来“撤销”令牌：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token comment">// 撤销所有令牌...</span></span>
<span class="line"><span class="token variable">$user</span><span class="token operator">-></span><span class="token function">tokens</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 撤销用于认证当前请求的令牌...</span></span>
<span class="line"><span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">currentAccessToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 撤销特定令牌...</span></span>
<span class="line"><span class="token variable">$user</span><span class="token operator">-></span><span class="token function">tokens</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">where</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'id'</span><span class="token punctuation">,</span> <span class="token variable">$tokenId</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="令牌过期" tabindex="-1"><a class="header-anchor" href="#令牌过期"><span>令牌过期</span></a></h3>
<p>默认情况下，Sanctum 令牌永不过期，只能通过<a href="#revoking-tokens">撤销令牌</a>来使其失效。然而，如果你希望为你的应用程序的 API 令牌配置一个过期时间，你可以通过在应用程序的 <code v-pre>sanctum</code> 配置文件中定义的 <code v-pre>expiration</code> 配置选项来实现。此配置选项定义了发行令牌被视为过期的分钟数：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token string single-quoted-string">'expiration'</span> <span class="token operator">=></span> <span class="token number">525600</span><span class="token punctuation">,</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>如果你希望独立指定每个令牌的过期时间，你可以通过将过期时间作为 <code v-pre>createToken</code> 方法的第三个参数来提供：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token variable">$user</span><span class="token operator">-></span><span class="token function">createToken</span><span class="token punctuation">(</span></span>
<span class="line">    <span class="token string single-quoted-string">'token-name'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'*'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">addWeek</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span>
<span class="line"><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">plainTextToken</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果你为你的应用程序配置了令牌过期时间，你可能还希望<a href="https://learnku.com/docs/laravel/11.x/scheduling" target="_blank" rel="noopener noreferrer">安排一个任务</a>来清理应用程序中已过期的令牌。幸运的是，Sanctum 提供了一个 <code v-pre>sanctum:prune-expired</code> Artisan 命令，你可以用它来完成这项工作。例如，你可以配置一个定时任务，删除至少已过期24小时的所有过期令牌数据库记录：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>Facades<span class="token punctuation">\</span>Schedule</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Schedule</span><span class="token operator">::</span><span class="token function">command</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'sanctum:prune-expired --hours=24'</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">daily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="单页面应用认证-1" tabindex="-1"><a class="header-anchor" href="#单页面应用认证-1"><span>单页面应用认证</span></a></h2>
<p>Sanctum 也旨在提供一种简单的方法来认证需要与 Laravel 驱动的 API 通信的单页面应用（SPA）。这些 SPA 可能与你的 Laravel 应用存在于同一个仓库中，或者可能是一个完全独立的仓库。</p>
<p>对于这个功能，Sanctum 不使用任何形式的令牌。相反，Sanctum 使用 Laravel 内置的基于 Cookie 的会话认证服务。这种认证方式提供了 CSRF 保护、会话认证的好处，同时也防止了通过 XSS 泄露认证凭证。</p>
<blockquote>
<p>[!WARNING]<br>
为了进行认证，你的 SPA 和 API 必须共享相同的顶级域名。然而，它们可以放在不同的子域上。此外，你应确保发送 <code v-pre>Accept: application/json</code> 头和 <code v-pre>Referer</code> 或 <code v-pre>Origin</code> 头中的任意一个与你的请求一起。</p>
</blockquote>
<h3 id="配置-2" tabindex="-1"><a class="header-anchor" href="#配置-2"><span>配置</span></a></h3>
<h4 id="配置你的首方域名" tabindex="-1"><a class="header-anchor" href="#配置你的首方域名"><span>配置你的首方域名</span></a></h4>
<p>首先，你应该配置你的 SPA 将从哪些域名发出请求。你可以在你的 <code v-pre>sanctum</code> 配置文件中使用 <code v-pre>stateful</code> 配置选项来配置这些域名。此配置设置决定了哪些域名在向你的 API 发出请求时将保持“有状态”的认证，使用 Laravel 会话 Cookie。</p>
<blockquote>
<p>[!WARNING]<br>
如果你是通过包含端口的 URL（如 <code v-pre>127.0.0.1:8000</code>）访问你的应用程序，你应确保你在域名中包含端口号。</p>
</blockquote>
<h4 id="sanctum-中间件" tabindex="-1"><a class="header-anchor" href="#sanctum-中间件"><span>Sanctum 中间件</span></a></h4>
<p>接下来，你应该指导 Laravel 让来自你的 SPA 的请求可以使用 Laravel 的会话 Cookie 进行认证，同时允许来自第三方或移动应用的请求使用 API 令牌进行认证。这可以通过在你的应用程序的 <code v-pre>bootstrap/app.php</code> 文件中调用 <code v-pre>statefulApi</code> 中间件方法轻松完成：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token operator">-></span><span class="token function">withMiddleware</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Middleware</span> <span class="token variable">$middleware</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$middleware</span><span class="token operator">-></span><span class="token function">statefulApi</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="cors-和-cookies" tabindex="-1"><a class="header-anchor" href="#cors-和-cookies"><span>CORS 和 Cookies</span></a></h4>
<p>如果你在尝试从执行在不同子域上的 SPA 认证你的应用程序时遇到问题，你可能配置错误了你的 CORS（跨源资源共享）或会话 Cookie 设置。</p>
<p><code v-pre>config/cors.php</code> 配置文件默认情况下不会发布。如果你需要自定义 Laravel 的 CORS 选项，你应该使用 <code v-pre>config:publish</code> Artisan 命令发布完整的 <code v-pre>cors</code> 配置文件：</p>
<div class="language-bash line-numbers-mode" data-highlighter="prismjs" data-ext="sh" data-title="sh"><pre v-pre class="language-bash"><code><span class="line">php artisan config:publish cors</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>接下来，你应确保你的应用程序的 CORS 配置返回 <code v-pre>Access-Control-Allow-Credentials</code> 头，并且其值为 <code v-pre>True</code>。这可以通过在你的应用程序的 <code v-pre>config/cors.php</code> 配置文件中设置 <code v-pre>supports_credentials</code> 选项为 <code v-pre>true</code> 来实现。</p>
<p>此外，你应该在你的应用程序的全局 <code v-pre>axios</code> 实例上启用 <code v-pre>withCredentials</code> 和 <code v-pre>withXSRFToken</code> 选项。通常，这应该在你的 <code v-pre>resources/js/bootstrap.js</code> 文件中执行。如果你没有使用 Axios 从前端发起 HTTP 请求，你应该在你自己的 HTTP 客户端上执行等效配置：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">axios<span class="token punctuation">.</span>defaults<span class="token punctuation">.</span>withCredentials <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span>
<span class="line">axios<span class="token punctuation">.</span>defaults<span class="token punctuation">.</span>withXSRFToken <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，你应该确保你的应用程序的会话 Cookie 域配置支持你根域名的任何子域。你可以通过在你的应用程序的 <code v-pre>config/session.php</code> 配置文件中为域名前加上一个点 <code v-pre>.</code> 来实现这一点：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token string single-quoted-string">'domain'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'.domain.com'</span><span class="token punctuation">,</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3 id="认证流程" tabindex="-1"><a class="header-anchor" href="#认证流程"><span>认证流程</span></a></h3>
<h4 id="csrf-保护" tabindex="-1"><a class="header-anchor" href="#csrf-保护"><span>CSRF 保护</span></a></h4>
<p>为了认证你的单页面应用（SPA），你的 SPA 的“登录”页面首先应该向 <code v-pre>/sanctum/csrf-cookie</code> 端点发起请求，以初始化应用程序的 CSRF 保护：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/sanctum/csrf-cookie'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token comment">// 登录...</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在此请求期间，Laravel 会设置一个包含当前 CSRF 令牌的 <code v-pre>XSRF-TOKEN</code> Cookie。然后，这个令牌应该在后续请求中通过 <code v-pre>X-XSRF-TOKEN</code> 头传递，一些 HTTP 客户端库如 Axios 和 Angular HttpClient 会自动为你做这件事。如果你的 JavaScript HTTP 库没有为你设置这个值，你需要手动设置 <code v-pre>X-XSRF-TOKEN</code> 头，以匹配由此路由设置的 <code v-pre>XSRF-TOKEN</code> Cookie 的值。</p>
<h4 id="登录" tabindex="-1"><a class="header-anchor" href="#登录"><span>登录</span></a></h4>
<p>一旦初始化了 CSRF 保护，你应该向你的 Laravel 应用的 <code v-pre>/login</code> 路径发送一个 <code v-pre>POST</code> 请求。这个 <code v-pre>/login</code> 路径可以<a href="https://learnku.com/docs/laravel/11.x/authentication#authenticating-users" target="_blank" rel="noopener noreferrer">手动实现</a>或使用无头认证包如 <a href="https://learnku.com/docs/laravel/11.x/fortify" target="_blank" rel="noopener noreferrer">Laravel Fortify</a>。</p>
<p>如果登录请求成功，你将被认证，随后对你的应用程序路由的请求将通过 Laravel 应用颁发给你的客户端的会话 Cookie 自动认证。此外，由于你的应用已经向 <code v-pre>/sanctum/csrf-cookie</code> 路径发起了请求，只要你的 JavaScript HTTP 客户端在 <code v-pre>X-XSRF-TOKEN</code> 头中发送 <code v-pre>XSRF-TOKEN</code> Cookie 的值，后续请求应自动接收 CSRF 保护。</p>
<p>当然，如果你的用户会话因活动不足而过期，随后对 Laravel 应用的请求可能会收到 401 或 419 HTTP 错误响应。在这种情况下，你应该将用户重定向到你的 SPA 的登录页面。</p>
<blockquote>
<p>[!WARNING]<br>
你可以自行编写你的 <code v-pre>/login</code> 端点；然而，你应该确保它使用 Laravel 提供的标准<a href="https://learnku.com/docs/laravel/11.x/authentication#authenticating-users" target="_blank" rel="noopener noreferrer">基于会话的认证服务</a>来认证用户。通常，这意味着使用 <code v-pre>web</code> 认证守卫。</p>
</blockquote>
<h3 id="保护路由-1" tabindex="-1"><a class="header-anchor" href="#保护路由-1"><span>保护路由</span></a></h3>
<p>为了保护路由，确保所有进来的请求都必须经过认证，你应该在你的 <code v-pre>routes/api.php</code> 文件中为你的 API 路由附加 <code v-pre>sanctum</code> 认证守卫。这个守卫将确保进来的请求要么是来自你的 SPA 的有状态认证请求，要么如果是来自第三方的请求，包含一个有效的 API 令牌头：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/user'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">return</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">middleware</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="授权私有广播频道" tabindex="-1"><a class="header-anchor" href="#授权私有广播频道"><span>授权私有广播频道</span></a></h3>
<p>如果你的 SPA 需要与<a href="https://learnku.com/docs/laravel/11.x/broadcasting#authorizing-channels" target="_blank" rel="noopener noreferrer">私有/存在广播频道</a>进行认证，你应该从你的应用程序的 <code v-pre>bootstrap/app.php</code> 文件中的 <code v-pre>withRouting</code> 方法中移除 <code v-pre>channels</code> 条目。相反，你应该调用 <code v-pre>withBroadcasting</code> 方法，以便为你的应用程序的广播路由指定正确的中间件：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token class-name static-context">Application</span><span class="token operator">::</span><span class="token function">configure</span><span class="token punctuation">(</span><span class="token argument-name">basePath</span><span class="token punctuation">:</span> <span class="token function">dirname</span><span class="token punctuation">(</span><span class="token constant">__DIR__</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">    <span class="token operator">-></span><span class="token function">withRouting</span><span class="token punctuation">(</span></span>
<span class="line">        <span class="token argument-name">web</span><span class="token punctuation">:</span> <span class="token constant">__DIR__</span><span class="token operator">.</span><span class="token string single-quoted-string">'/../routes/web.php'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token comment">// ...</span></span>
<span class="line">    <span class="token punctuation">)</span></span>
<span class="line">    <span class="token operator">-></span><span class="token function">withBroadcasting</span><span class="token punctuation">(</span></span>
<span class="line">        <span class="token constant">__DIR__</span><span class="token operator">.</span><span class="token string single-quoted-string">'/../routes/channels.php'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">[</span><span class="token string single-quoted-string">'prefix'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'api'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'middleware'</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'api'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来，为了使 Pusher 的授权请求成功，你需要在初始化 <a href="https://learnku.com/docs/laravel/11.x/broadcasting#client-side-installation" target="_blank" rel="noopener noreferrer">Laravel Echo</a> 时提供一个自定义的 Pusher <code v-pre>authorizer</code>。这允许你的应用配置 Pusher 使用<a href="#cors-and-cookies">正确配置了跨域请求的</a> <code v-pre>axios</code> 实例：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">window<span class="token punctuation">.</span>Echo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Echo</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">broadcaster</span><span class="token operator">:</span> <span class="token string">"pusher"</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">cluster</span><span class="token operator">:</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VITE_PUSHER_APP_CLUSTER</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">encrypted</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">key</span><span class="token operator">:</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">VITE_PUSHER_APP_KEY</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token function-variable function">authorizer</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">channel<span class="token punctuation">,</span> options</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span>
<span class="line">        <span class="token keyword">return</span> <span class="token punctuation">{</span></span>
<span class="line">            <span class="token function-variable function">authorize</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">socketId<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span>
<span class="line">                axios<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/api/broadcasting/auth'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span></span>
<span class="line">                    <span class="token literal-property property">socket_id</span><span class="token operator">:</span> socketId<span class="token punctuation">,</span></span>
<span class="line">                    <span class="token literal-property property">channel_name</span><span class="token operator">:</span> channel<span class="token punctuation">.</span>name</span>
<span class="line">                <span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line">                <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">response</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span>
<span class="line">                    <span class="token function">callback</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">                <span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line">                <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">error</span> <span class="token operator">=></span> <span class="token punctuation">{</span></span>
<span class="line">                    <span class="token function">callback</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">            <span class="token punctuation">}</span></span>
<span class="line">        <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="移动应用认证" tabindex="-1"><a class="header-anchor" href="#移动应用认证"><span>移动应用认证</span></a></h2>
<p>你也可以使用 Sanctum 令牌来认证你的移动应用对 API 的请求。认证移动应用请求的过程与认证第三方 API 请求类似；然而，在发放 API 令牌的方式上有些许差异。</p>
<h3 id="发放-api-令牌-1" tabindex="-1"><a class="header-anchor" href="#发放-api-令牌-1"><span>发放 API 令牌</span></a></h3>
<p>首先，创建一个路由，接受用户的电子邮件/用户名、密码和设备名称，然后用这些凭证交换一个新的 Sanctum 令牌。此端点所需的“设备名称”仅为信息目的，可以是你希望的任何值。通常，设备名称应该是用户能认出的名称，比如“Nuno 的 iPhone 12”。</p>
<p>通常，你会从移动应用的“登录”屏幕向令牌端点发出请求。该端点将返回纯文本 API 令牌，该令牌随后可存储在移动设备上并用于发起额外的 API 请求：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Models<span class="token punctuation">\</span>User</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Support<span class="token punctuation">\</span>Facades<span class="token punctuation">\</span>Hash</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Validation<span class="token punctuation">\</span>ValidationException</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/sanctum/token'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">validate</span><span class="token punctuation">(</span><span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'email'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'required|email'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'password'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'required'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'device_name'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'required'</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$user</span> <span class="token operator">=</span> <span class="token class-name static-context">User</span><span class="token operator">::</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'email'</span><span class="token punctuation">,</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token property">email</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">first</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token variable">$user</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token class-name static-context">Hash</span><span class="token operator">::</span><span class="token function">check</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token operator">-></span><span class="token property">password</span><span class="token punctuation">,</span> <span class="token variable">$user</span><span class="token operator">-></span><span class="token property">password</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">        <span class="token keyword">throw</span> <span class="token class-name static-context">ValidationException</span><span class="token operator">::</span><span class="token function">withMessages</span><span class="token punctuation">(</span><span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'email'</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'提供的凭证不正确。'</span><span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line">    <span class="token keyword">return</span> <span class="token variable">$user</span><span class="token operator">-></span><span class="token function">createToken</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token operator">-></span><span class="token property">device_name</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">plainTextToken</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当移动应用使用令牌向你的应用发起 API 请求时，应该在 <code v-pre>Authorization</code> 头中作为 <code v-pre>Bearer</code> 令牌传递该令牌。</p>
<blockquote>
<p>[!NOTE]<br>
在为移动应用发放令牌时，你也可以自由指定<a href="#token-abilities">令牌权限</a>。</p>
</blockquote>
<h3 id="保护路由-2" tabindex="-1"><a class="header-anchor" href="#保护路由-2"><span>保护路由</span></a></h3>
<p>如前文所述，你可以通过在路由上附加 <code v-pre>sanctum</code> 认证守卫来保护路由，确保所有进入的请求都必须经过认证：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/user'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">return</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">middleware</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'auth:sanctum'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="撤销令牌-1" tabindex="-1"><a class="header-anchor" href="#撤销令牌-1"><span>撤销令牌</span></a></h3>
<p>为了允许用户撤销发放给移动设备的 API 令牌，你可以在你的网页应用的“账户设置”部分列出这些令牌，并提供一个“撤销”按钮。当用户点击“撤销”按钮时，你可以从数据库中删除该令牌。记住，你可以通过 <code v-pre>Laravel\Sanctum\HasApiTokens</code> trait 提供的 <code v-pre>tokens</code> 关系访问用户的 API 令牌：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token comment">// 撤销所有令牌...</span></span>
<span class="line"><span class="token variable">$user</span><span class="token operator">-></span><span class="token function">tokens</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 撤销特定令牌...</span></span>
<span class="line"><span class="token variable">$user</span><span class="token operator">-></span><span class="token function">tokens</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">where</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'id'</span><span class="token punctuation">,</span> <span class="token variable">$tokenId</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="测试" tabindex="-1"><a class="header-anchor" href="#测试"><span>测试</span></a></h2>
<p>在测试时，可以使用 <code v-pre>Sanctum::actingAs</code> 方法来认证用户，并指定应授予其令牌的权限：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Models<span class="token punctuation">\</span>User</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>Sanctum</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token function">test</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'task list can be retrieved'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token class-name static-context">Sanctum</span><span class="token operator">::</span><span class="token function">actingAs</span><span class="token punctuation">(</span></span>
<span class="line">        <span class="token class-name static-context">User</span><span class="token operator">::</span><span class="token function">factory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">[</span><span class="token string single-quoted-string">'view-tasks'</span><span class="token punctuation">]</span></span>
<span class="line">    <span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$response</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/api/task'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$response</span><span class="token operator">-></span><span class="token function">assertOk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Models<span class="token punctuation">\</span>User</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">Laravel<span class="token punctuation">\</span>Sanctum<span class="token punctuation">\</span>Sanctum</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">test_task_list_can_be_retrieved</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span></span>
<span class="line"><span class="token punctuation">{</span></span>
<span class="line">    <span class="token class-name static-context">Sanctum</span><span class="token operator">::</span><span class="token function">actingAs</span><span class="token punctuation">(</span></span>
<span class="line">        <span class="token class-name static-context">User</span><span class="token operator">::</span><span class="token function">factory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">[</span><span class="token string single-quoted-string">'view-tasks'</span><span class="token punctuation">]</span></span>
<span class="line">    <span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$response</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/api/task'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$response</span><span class="token operator">-></span><span class="token function">assertOk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果你想授予令牌所有权限，应在 <code v-pre>actingAs</code> 方法提供的权限列表中包含 <code v-pre>*</code>：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token class-name static-context">Sanctum</span><span class="token operator">::</span><span class="token function">actingAs</span><span class="token punctuation">(</span></span>
<span class="line">    <span class="token class-name static-context">User</span><span class="token operator">::</span><span class="token function">factory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">[</span><span class="token string single-quoted-string">'*'</span><span class="token punctuation">]</span></span>
<span class="line"><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>本译文仅用于学习和交流目的，转载请务必注明文章译者、出处、和本文链接<br>
我们的翻译工作遵照 <a href="https://learnku.com/docs/guide/cc4.0/6589" target="_blank" rel="noopener noreferrer">CC 协议</a>，如果我们的工作有侵犯到您的权益，请及时联系我们。</p>
</blockquote>
<hr>
<blockquote>
<p>原文地址：<a href="https://learnku.com/docs/laravel/11.x/sanctummd/16732" target="_blank" rel="noopener noreferrer">https://learnku.com/docs/laravel/11.x/sa...</a></p>
<p>译文地址：<a href="https://learnku.com/docs/laravel/11.x/sanctummd/16732" target="_blank" rel="noopener noreferrer">https://learnku.com/docs/laravel/11.x/sa...</a></p>
</blockquote>
</div></template>


